#include "log.h"
#include "blockqueue.h"
#include <cstdio>
#include <memory>
#include <mutex>
#include <sys/select.h>
#include <sys/stat.h>
#include <thread>
#include <sys/time.h>
#include <stdarg.h>
#include <cstdio>
#include <ctime>

using namespace std;

Log::Log() {
    lineCount_ = 0;
    // 异步写日志（就是专门开一个用于写日志的线程）
    isAsync_ = false;
    writeThread_ = nullptr;
    deque_ = nullptr;
    toDay_ = 0;
    fp_ = nullptr;
}

Log::~Log() {
    // 如果有写线程，并且这个写线程还未汇入
    if(writeThread_ && writeThread_->joinable()) {
        // 队列不为空就不断刷新队列中的数据（将队列中现有的数据先用掉）
        while(!deque_->empty()) {
            deque_->flush();
        }
        // 关闭队列
        deque_->Close();
        // 汇入线程（等待该线程资源回收）
        writeThread_->join();
    }
    if(fp_) {
        lock_guard<mutex> locker(mtx_);
        flush();
        fclose(fp_);
    }
}

int Log::GetLevel() {
    lock_guard<mutex> locker(mtx_);
    return level_;
}

void Log::init(int level = 1, const char* path, const char* suffix,
    int maxQueueSize) {
    isOpen_ = true;
    level_ = level;
    if(maxQueueSize > 0) {
        isAsync_ = true; // 默认异步？
        // 队列已经被创建了，直接用新队列替换老队列
        /*
            疑惑：
            这么写不会有内存泄漏问题嘛
            新的deque直接覆盖旧的deque
        */
        if(!deque_) {
            unique_ptr<BlockDeque<string>> newDeque(new BlockDeque<string>);
            deque_ = move(newDeque);

            unique_ptr<thread> newThread(new thread(FlushLogThread));
            writeThread_ = move(newThread);
        }
    }
    // 不设置队列，即达即写
    else {
        isAsync_ = false;
    }

    lineCount_ = 0;

    time_t timer = time(nullptr); // 用于获取当前时间
    tm* sysTime = localtime(&timer);
    tm t = *sysTime;

    path_ = path;
    suffix_ = suffix;
    char fileName[LOG_NAME_LEN] = {};
    snprintf(fileName, LOG_NAME_LEN - 1, "%s/%04d_%02d_%02d%s", 
            path_, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, suffix_);

    toDay_ = t.tm_mday;

    {
        lock_guard<mutex> locker(mtx_);
        // 重置缓冲区（重置读写指针的位置）
        buff_.RetrieveAll();

        // 已有FILE
        if(fp_) {
            flush();
            fclose(fp_);
        }

        fp_ = fopen(fileName, "a");
        if(fp_ == nullptr) {
            mkdir(path, 0777);
            fp_ = fopen(fileName, "a");
        }
        assert(fp_ != nullptr);
    }
}

void Log::write(int level, const char *format, ...) {
    struct timeval now = {0, 0};
    gettimeofday(&now, nullptr);
    time_t tSec = now.tv_sec;
    struct tm *sysTime = localtime(&tSec);
    struct tm t = *sysTime;
    va_list vaList;

    /* 日志日期 日志行数 */
    if (toDay_ != t.tm_mday || (lineCount_ && (lineCount_  %  MAX_LINES == 0)))
    {
        unique_lock<mutex> locker(mtx_);
        locker.unlock();
        
        char newFile[LOG_NAME_LEN];
        char tail[36] = {0};
        snprintf(tail, 36, "%04d_%02d_%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);

        if (toDay_ != t.tm_mday)
        {
            snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s%s", path_, tail, suffix_);
            toDay_ = t.tm_mday;
            lineCount_ = 0;
        }
        else {
            snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s-%d%s", path_, tail, (lineCount_  / MAX_LINES), suffix_);
        }
        
        locker.lock();
        flush();
        fclose(fp_);
        fp_ = fopen(newFile, "a");
        assert(fp_ != nullptr);
    }

    {
        unique_lock<mutex> locker(mtx_);
        lineCount_++;
        int n = snprintf(buff_.BeginWrite(), 128, "%d-%02d-%02d %02d:%02d:%02d.%06ld ",
                    t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
                    t.tm_hour, t.tm_min, t.tm_sec, now.tv_usec);
                    
        buff_.HasWritten(n);
        AppendLogLevelTitle_(level);

        va_start(vaList, format);
        int m = vsnprintf(buff_.BeginWrite(), buff_.WritableBytes(), format, vaList);
        va_end(vaList); // 别放了释放资源

        buff_.HasWritten(m);
        buff_.Append("\n\0", 2);

        if(isAsync_ && deque_ && !deque_->full()) {
            deque_->push_back(buff_.RetrieveAllToStr());
        } else {
            fputs(buff_.Peek(), fp_);
        }
        buff_.RetrieveAll();
    }
}

void Log::AppendLogLevelTitle_(int level) {
    switch(level) {
        case 0:
            // 英文1字节，中文2字节，C数组末尾还有个隐藏的'\0'表示结束符
            buff_.Append("[debug]：", 9);
            break;
        case 1:
            buff_.Append("[info]：", 9);
            break;
        case 2:
            buff_.Append("[warn：]", 9);
            break;
        case 3:
            buff_.Append("[error]：", 9);
            break;
        default:
            buff_.Append("[info]：", 9);
            break;
    }
}

void Log::flush() {
    string str = "";
    while(deque_->pop(str)) {
        lock_guard<mutex> locker(mtx_);
        // 需要转换成C风格的字符数组，这是fputs函数的设计所导致的
        fputs(str.c_str(), fp_);
    }
}

Log* Log::Instance() {
    static Log inst;
    return& inst;
}

void Log::FlushLogThread() {
    Log::Instance()->AsyncWrite_();
}


void Log::AsyncWrite_() {
    string str = "";
    while(deque_->pop(str)) {
        lock_guard<mutex> locker(mtx_);
        fputs(str.c_str(), fp_);
    }
}